/**
 1、查询的逻辑 A
 2、增加（查询）+删除（查询）B
 3、展示所有数据-->排序 C
 4、文件读取与写入 D
 */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//定义数据文件为符号常量。
#define DATA_FILE "./data.dat"
#define MAX_SIZE 100
#define DORM_MAX_NUM 8 // 一个宿舍最多住多少人
//定义住宿信息结构体。
typedef struct dormitory
{
    char dormID[10];
    char studentID[10];
    char sex[10]; // 0:男生，1：女生
    char Name[10];
} dormitory;
//定义住宿信息链表结点，包括 dormitory 型的数据域和指向下一个结点的指针域。
typedef struct node_dormitory
{
    dormitory data;
    struct node_dormitory *next;
} node_dormitory, *p_node_dormitory;
//定义住宿信息链表的头指针为全局变量。
p_node_dormitory headDormitory;
//定义 3 个指针数组，分别按照关键字学号、姓名和宿舍号顺序存储住宿信息的结点地址。
dormitory *arrayDormID[MAX_SIZE], *arrayStudentID[MAX_SIZE], *arrayName[MAX_SIZE];
int countRecord;



//下列函数是输出住宿信息时要调用的函数，为了输出格式便于查看，在输出住宿信息之前输
//出住宿信息的表头（宿舍号、学号、姓名）。
void PrintTitle()
{
    printf("---------------------------------------------------\n");
    printf("      宿舍号 |        学号 |        名字 |       性别 \n");
}
//下列函数是输出结点 p 所代表的住宿信息的内容（宿舍号、学号、姓名）。
void PrintDormitory(dormitory p)
{
    printf(" %10s", p.dormID);
    printf(" | %10s", p.studentID);
    printf(" | %10s", p.Name);
    if (strcmp(p.sex, "0") == 0) {
        char *s = "男";
        printf(" | %10s\n", s);
    } else {
        char *s = "女";
        printf(" | %10s\n", s);
    }
    
}



//下列函数是排序函数，功能是根据宿舍号进行排序，排序时住宿信息链表并不改变，只是把
//宿舍指针数组中的内容根据指针所指的结点的宿舍号大小进行排序。C
void sortWithDormID()
{
    int i, j, k;
    dormitory *tmp;
    for(i = 0; i < countRecord - 1; i++)
    {
        k = i;
        for(j = i + 1; j < countRecord; j++)
            if(strcmp(arrayDormID[j]->dormID, arrayDormID[k]->dormID) < 0)
                k = j;
        if(k != i)
        {
            tmp = arrayDormID[i];
            arrayDormID[i] = arrayDormID[k];
            arrayDormID[k] = tmp;
        }
    }
}
//下列函数是排序函数，功能是根据学号进行排序，排序时住宿信息链表并不改变，只是把学
//号指针数组中的内容根据指针所指的结点的学号大小进行排序 C
void sortWithStudentID()
{
    int i, j, k;
    dormitory *tmp;
    for(i = 0; i < countRecord - 1; i++)
    {
        k = i;
        for(j = i + 1; j < countRecord; j++)
            if(strcmp(arrayStudentID[j]->studentID, arrayStudentID[k]->studentID) < 0)
                k = j;
        if(k != i)
        {
            tmp = arrayStudentID[i];
            arrayStudentID[i] = arrayStudentID[k];
            arrayStudentID[k] = tmp;
        }
    }
}
//下列函数是排序函数，功能是根据姓名进行排序，排序时住宿信息链表并不改变，只是把姓
//名指针数组中的内容根据指针所指的结点的姓名大小进行排序。C
void sortWithName()
{
    int i, j, k;
    dormitory *tmp;
    for(i = 0; i < countRecord - 1; i++)
    {
        k = i;
        for(j = i + 1; j < countRecord; j++)
            if(strcmp(arrayName[j]->Name, arrayName[k]->Name) < 0)
                k = j;
        if(k != i)
        {
            tmp = arrayName[i];
            arrayName[i] = arrayName[k];
            arrayName[k] = tmp;
        }
    }
}

//下列函数是根据姓名查找函数，功能是采用二分查找法在姓名指针数组中查找结点。A
void searchWithName(char *key)
{
    int low, high, mid, i, matchCount; //matchCount 为匹配记录数目
    low = 0;
    high = countRecord - 1;
    matchCount = 0;
    while(low <= high)
    {
        mid = (low + high) / 2;
        if(strcmp(arrayName[mid]->Name, key) == 0)
        {
            PrintTitle();
            i = mid - 1;
            while(i >= low)
            {
                if(strcmp(arrayName[i]->Name, key) == 0)
                    i--;
                else
                    break;
            }
            low = i + 1; //low 此时为匹配的第一条记录下标
            i = mid;
            while(i <= high)
            {
                if(strcmp(arrayName[i]->Name, key) == 0)
                    i++;
                else
                    break;
            }
            high = i - 1; //high 此时为匹配的最后一条记录下标
            matchCount = high - low + 1;
            for(i = low; i <=high; i++)
                PrintDormitory(*arrayName[i]);
            printf("\n共找到%d条数据\n", matchCount);
            getchar();
            return;
        }
        else if(strcmp(arrayName[mid]->Name, key) < 0)
            low = mid + 1;
        else
            high = mid - 1;
    }
    printf("\n404 没有找到");
    getchar();
}
//下列函数是根据学号查找函数，功能是采用二分查找法在学号指针数组中查找结点。A
void searchWithStudentID(char *key)
{
    int low, high, mid;
    low = 0;
    high = countRecord - 1;
    while(low <= high)
    {
        mid = (low + high) / 2;
        if(strcmp(arrayStudentID[mid]->studentID, key) == 0)
        {
            PrintTitle();
            PrintDormitory(*arrayStudentID[mid]);
            printf("\n找到1条数据\n");
            getchar();
            return;
        }
        else if(strcmp(arrayStudentID[mid]->studentID, key) < 0)
            low = mid + 1;
        else
            high = mid - 1;
    }
    printf("\n404 没有找到");
    getchar();
}
//下列函数是根据宿舍号查找函数，功能是采用二分查找法在宿舍指针数组中查找结点。A
void searchWithDormID(char *key)
{
    int low, high, mid, i, matchCount; //matchCount 为匹配记录数目
    low = 0;
    high = countRecord - 1;
    matchCount = 0;
    while(low <= high)
    {
        mid = (low + high) / 2;
        if(strcmp(arrayDormID[mid]->dormID, key) == 0)
        {
            PrintTitle();
            i = mid - 1;
            while(i >= low)
            {
                if(strcmp(arrayDormID[i]->dormID, key) == 0)
                    i--;
                else
                    break;
            }
            low = i + 1; //low 此时为匹配的第一条记录下标
            i = mid;
            while(i <= high)
            {
                if(strcmp(arrayDormID[i]->dormID, key) == 0)
                    i++;
                else
                    break;
            }
            high = i - 1; //high 此时为匹配的最后一条记录下标
            matchCount = high - low + 1;
            for(i = low; i <=high; i++)
                PrintDormitory(*arrayDormID[i]);
            printf("\n共找到%d条数据!\n", matchCount);
            getchar();
            return;
        }
        else if(strcmp(arrayDormID[mid]->dormID, key) < 0)
            low = mid + 1;
        else
            high = mid - 1;
    }
    printf("\n404 没有找到");
    getchar();
}
//下列函数是写入文件，功能是把住宿信息链表中的内容保存到数据文件中。添加、修改、删
//除链表的函数完成操作后，只是链表的内容改变了，并没有把修改后的内容写入到数据文件中，
//只有调用下列函数，修改后的内容才写入数据文件中。D
void writeDataToFile()
{
    FILE *fp;
    p_node_dormitory pNodeDormitory;
    int count = 0;
    if((fp = fopen(DATA_FILE, "wb+")) == NULL)
    {
        printf("文件已损坏!\n");
        return;
    }
    pNodeDormitory = headDormitory;
    while(pNodeDormitory != NULL)
    {
        if(fwrite(&pNodeDormitory->data, sizeof(dormitory), 1, fp) != 1)
            printf("文件写入失败!\n");
        pNodeDormitory = pNodeDormitory->next;
        count++;
    }
    fclose(fp);
    printf("成功写入%d条数据!\n", count);
    getchar();
}
// 通过宿舍号查询到对应的信息  -1代表失败  0 代表添加成功 A
int jiaoyanDormNumAndSex(char *key, char *sex)
{
    // 男女不能混住
    int low, high, mid, index, matchCount; //matchCount 为匹配记录数目
    low = 0;
    high = countRecord - 1;
    matchCount = 0;
    while(low <= high)
    {
        mid = (low + high) / 2;
        if(strcmp(arrayDormID[mid]->dormID, key) == 0)
        {
            index = mid - 1;
            while(index >= low)
            {
                if(strcmp(arrayDormID[index]->dormID, key) == 0)
                    index--;
                else
                    break;
            }
            low = index + 1; //low 此时为匹配的第一条记录下标
            index = mid;
            while(index <= high)
            {
                if(strcmp(arrayDormID[index]->dormID, key) == 0)
                    index++;
                else
                    break;
            }
            high = index - 1; //high 此时为匹配的最后一条记录下标
            matchCount = high - low + 1;
            if (matchCount == DORM_MAX_NUM) {
                printf("\n添加失败😭\n");
                printf("\n%s号宿舍只能住%d个人，目前已经住满！\n", key, DORM_MAX_NUM);
                getchar();
                return -1;
            }
            for(index = low; index <=high; index++) {
                if (strcmp(arrayDormID[index]->sex, sex) != 0) {
                    printf("\n添加失败😭\n");
                    if (strcmp(arrayDormID[index]->sex, "0") == 0) {
                        printf("\n%s号宿舍是男宿舍不能住女生\n", key);
                    } else {
                        printf("\n%s号宿舍是女宿舍不能住男生\n", key);
                    }
                    
                    getchar();
                    return -1;
                } else {
                    low = 1;
                    high = 0;
                }
            }
        }
        else if(strcmp(arrayDormID[mid]->dormID, key) < 0)
            low = mid + 1;
        else
            high = mid - 1;
    }
    
    return 0;
    
}

//下列函数是添加住宿信息的函数，功能是在住宿信息链表的表头插入一个新的结点，并把该
//结点的地址分别添加到宿舍号指针数组、姓名指针数组和学号指针数组的末尾，然后把宿舍号指
//针数组、姓名指针数组和学号指针数组分别排序。B
void add()
{
    char dormID[10];
    char studentID[10];
    char name[10];
    char sex[10];
    printf("\n 宿舍号： ");
    scanf("%s", dormID);
    printf("\n 学号： ");
    scanf("%s", studentID);
    printf("\n 名字： ");
    scanf("%s", name);
    printf("\n 性别（0：男，1：女）： ");
    scanf("%s", sex);
    
    // 校验宿舍是否住满，男女不能混住
    if (jiaoyanDormNumAndSex(dormID, sex) == -1) return;
    

    
    int i, j, k;
    p_node_dormitory pNodeDormitory;
    pNodeDormitory = (p_node_dormitory)malloc(sizeof(node_dormitory));
    
    
    strcpy(pNodeDormitory->data.dormID, dormID);
    strcpy(pNodeDormitory->data.studentID, studentID);
    strcpy(pNodeDormitory->data.Name, name);
    strcpy(pNodeDormitory->data.sex, sex);
    
    if(headDormitory == NULL)
    {
        headDormitory = pNodeDormitory;
        pNodeDormitory->next = NULL;
    }
    else
    {
        pNodeDormitory->next = headDormitory;
        headDormitory = pNodeDormitory;
    }
    //添加一条记录后重新排序住宿号指针数组
    for(i = 0; i < countRecord; i++)
    {
        if(strcmp(arrayDormID[i]->dormID, &headDormitory->data.dormID) > 0)
            break;
    }
    for(j = countRecord; j > i; j--)
        arrayDormID[j] = arrayDormID[j - 1];
    arrayDormID[i] = &headDormitory->data;
    //添加一条记录后重新排序学号指针数组
    for(i = 0; i < countRecord; i++)
    {
        if(strcmp(arrayStudentID[i]->studentID, &headDormitory->data.studentID) > 0)
            break;
    }
    for(j = countRecord; j > i; j--)
        arrayStudentID[j] = arrayStudentID[j - 1];
    arrayStudentID[i] = &headDormitory->data;
    //添加一条记录后重新排序姓名指针数组
    for(i = 0; i < countRecord; i++)
    {
        if(strcmp(arrayName[i]->Name, &headDormitory->data.Name) > 0)
            break;
    }
    for(j = countRecord; j > i; j--)
        arrayName[j] = arrayName[j - 1];
    arrayName[i] = &headDormitory->data;
    countRecord++;
    PrintTitle();
    PrintDormitory(pNodeDormitory->data);
    getchar();
}
//下列函数是删除住宿信息的函数，功能是根据学号查找到要删除的结点，在信息链表中删除
//该结点，同时把宿舍号指针数组、姓名指针数组和学号指针数组中与该结点相关的指针域置为空。B
void delete()
{
    p_node_dormitory pNodeDormitory, p1;
    char sID[10];
    int i, j;
    p1 = headDormitory;
    printf("\n请输入要删除的学号：");
    scanf("%s", sID);
    if(strcmp(p1->data.studentID, sID) == 0)
    {
        headDormitory = p1->next;
        pNodeDormitory = p1;
    }
    else
    {
        pNodeDormitory = p1->next;
        while(pNodeDormitory != NULL)
        {
            if(strcmp(pNodeDormitory->data.studentID, sID) == 0)
            {
                p1->next = pNodeDormitory->next;
                break;
            }
            p1 = pNodeDormitory;
            pNodeDormitory = pNodeDormitory->next;
        }
    }
    if(pNodeDormitory)
    {
        //删除一条记录后重新排序住宿号指针数组
        for(i = 0; i < countRecord; i++)
            if(arrayDormID[i] == &pNodeDormitory->data)
                break;
        for(j = i; j < countRecord - 1; j++)
            arrayDormID[j] = arrayDormID[j + 1];
        //删除一条记录后重新排序学号指针数组
        for(i = 0; i < countRecord; i++)
            if(arrayStudentID[i] == &pNodeDormitory->data)
                break;
        for(j = i; j < countRecord - 1; j++)
            arrayStudentID[j] = arrayStudentID[j + 1];
        //删除一条记录后重新排序姓名指针数组
        for(i = 0; i < countRecord; i++)
            if(arrayName[i] == &pNodeDormitory->data)
                break;
        for(j = i; j < countRecord - 1; j++)
            arrayName[j] = arrayName[j + 1];
        free(pNodeDormitory);
        countRecord--;
        printf("\n学号%s已删除", sID);
    }
    else
        printf("\n404 没有该学生");
    getchar();
}
//下列函数是修改住宿信息函数，首先根据输入的学号找到要修改的住宿信息结点，然后修改
//该结点的数据域（宿舍号、学号、姓名）。B
void edit()
{
    p_node_dormitory pNodeDormitory;
    char sID[10];
    pNodeDormitory = headDormitory;
    printf("\n请输入你想编辑的学号");
    printf("\n学号： ");
    scanf("%s", sID);
    while(pNodeDormitory != NULL)
    {
        if(strcmp(pNodeDormitory->data.studentID, sID) == 0)
        {
            PrintTitle();
            PrintDormitory(pNodeDormitory->data);
            printf("\n 编辑学号： ");
            scanf("%s", pNodeDormitory->data.dormID);
            printf("\n 编辑宿舍号： ");
            scanf("%s", pNodeDormitory->data.studentID);
            printf("\n 编辑名字： ");
            scanf("%s", pNodeDormitory->data.Name);
            printf("\n 编辑性别（0：男，1：女）： ");
            scanf("%s", pNodeDormitory->data.Name);
            printf("\n\n\n");
            PrintTitle();
            PrintDormitory(pNodeDormitory->data);
            printf("\n学号%s已编辑成功!", pNodeDormitory->data.studentID);
            getchar();
            return;
        }
        pNodeDormitory = pNodeDormitory->next;
    }
    printf("\n404 找不到对应的学生");
    getchar();
}
//下列函数是读入文件的函数，把事先保存在数据文件中的住宿信息读入到内存中，创建住宿
//信息组成的单链表，同时把链表结点的地址保存到住宿号指针数组、学号指针数组和姓名指针数
//组中。D
void readFile()
{
    FILE *fp;
    p_node_dormitory pNodeDormitory;
    int size;
    countRecord = 0;
    fp = fopen(DATA_FILE, "rb");
    fseek(fp, 0, 2); //得到文件大小
    size = ftell(fp);
    rewind(fp);
    while(size != 0)
    {
        size -= sizeof(struct dormitory);
        pNodeDormitory = (p_node_dormitory)malloc(sizeof(node_dormitory));
        if(fread(&pNodeDormitory->data, sizeof(struct dormitory), 1, fp) != 1)
            exit(0);
        pNodeDormitory->next = headDormitory;
        headDormitory = pNodeDormitory;
        arrayDormID[countRecord] = &headDormitory->data;
        arrayStudentID[countRecord] = &headDormitory->data;
        arrayName[countRecord] = &headDormitory->data;
        countRecord++;
    }
    fclose(fp);
    sortWithDormID();
    sortWithStudentID();
    sortWithName();
    printf("获取到%d条数据!\n", countRecord);
    getchar();
}
//下列函数是显示住宿信息的函数，功能是把目前住宿信息链表中的所有结点内容显示出来。
//首先按照链表顺序显示链表内容（宿舍号、学号、姓名），然后分别按照姓名、学号、宿舍号为关
//键字排序后显示住宿信息（宿舍号、学号、姓名）。C
void view()
{
    int count = 0;
    p_node_dormitory pNodeDormitory;
    pNodeDormitory = headDormitory;
    PrintTitle();
    while(pNodeDormitory != NULL)
    {
        PrintDormitory(pNodeDormitory->data);
        pNodeDormitory = pNodeDormitory->next;
        count++;
    }
    printf("共有%d条数据\n", count);
    printf("\n\n通过名字排序\n");
    PrintTitle();
    for(count = 0; count < countRecord; count++)
    {
        PrintDormitory(*arrayName[count]);
    }
    printf("\n\n通过学号排序\n");
    PrintTitle();
    for(count = 0; count < countRecord; count++)
    {
        PrintDormitory(*arrayStudentID[count]);
    }
    printf("\n\n通过宿舍号排序\n");
    PrintTitle();
    for(count = 0; count < countRecord; count++)
    {
        PrintDormitory(*arrayDormID[count]);
    }
    getchar();
}
//下列函数是查找函数，进入查找函数后再次进行选择查找的方式。A
void find(int type)
{
    char *typeName = NULL;
    switch(type)
    {
        case 1:
            typeName="学生名字"; break;
        case 2:
            typeName="学号"; break;
        case 3:
            typeName="宿舍号"; break;
    }
    char key[10];
    printf("\n请输入要查找的%s：", typeName);
    scanf("%s", key);
    switch(type)
    {
        case 1:
            searchWithName(key); break;
        case 2:
            searchWithStudentID(key); break;
        case 3:
            searchWithDormID(key); break;
    }
}
int main()
{
    int choice = 0;
    countRecord = 0;
    headDormitory = NULL;
    int num = 0;
    do
    {
//        system("cls");
        printf("\n\n");
        if (num == 0) {
            printf("．．．．＿＿＿＿．╭╮╭╮．＿＿＿＿．．．．．\n");
            printf("．．．．．．．．╭－┴┴★╮．．．．．．．．．\n");
            printf("．．．．．．．．│◎　　︵│．．．．．．．．\n");
            printf("．．．．※※※．╰○－－○╯．※※※．．．．\n");
            printf("．．．．．．欢迎使用宿舍管理系统．．．．．．\n");
        }
        num = 1;
        printf("\n----------------------------------\n\n");
        printf("1. 读取文件数据;\n");
        printf("2. 查看所有数据;\n");
        printf("3. 添加学生信息;\n");
        printf("4. 编辑学生信息;\n");
        printf("5. 删除学生信息;\n");
        printf("6. 通过学生名字查找信息;\n");
        printf("7. 通过学号查找信息;\n");
        printf("8. 通过宿舍号查找信息;\n");
        printf("9. 将所有数据写入到本地文件中;\n");
        printf("0. 退出.\n");
        printf("\n\n");
        printf("请选择:");
        scanf("%d", &choice);
        switch(choice)
        {
            case 1://读入文件
                readFile(); break;
            case 2://显示全部住宿信息 排序
                view(); break;
            case 3://添加一条住宿信息 查 增
                add(); break;
            case 4://修改一条住宿信息
                edit(); break;
            case 5://删除一条住宿信息
                delete(); break;
            case 6://根据姓名查找
                find(1); break;
            case 7://根据学号查找
                find(2); break;
            case 8://根据住宿号查找
                find(3); break;
            case 9://写入文件
                writeDataToFile(); break;
            case 0://退出系统
                break;
            default://输入错误
                printf("Your choice must between 0 to 9!");
        }
    }while(choice != 0);
    
    return 0;
}
